package com.patsnap.data.npd.dw.util;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.patsnap.data.npd.dw.repository.DBException;
import com.patsnap.one.etl.table.meta.annotation.AsTargetTableMeta;
import com.patsnap.one.etl.table.meta.annotation.FieldMeta;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Haoran.Hua
 * @date 2023/4/18 19:53
 */
public class MapperUtil {
    public static final String NO_NULL_MAPPING_JSON = "typeHandler=com.patsnap.data.dw.handler.NoNullFastjsonTypeHandler";

    public static LoadingCache<Class, Map<String, Field>> fieldMapCache = CacheBuilder.newBuilder()
            .build(new CacheLoader<Class, Map<String, Field>>() {
                @Override
                public Map<String, Field> load(Class t) throws Exception {
                    Set<Field> fields = ReflectionUtils.getAllFields(t, ReflectionUtils.withAnnotation(FieldMeta.class));
                    return fields.stream().collect(Collectors.toMap(field -> field.getAnnotation(FieldMeta.class).fieldName(), field -> field));
                }
            });


    public static <T> T buildQueryEntity(T entity) throws DBException {
        // 通过反射初始化一个新的对象
        try {
            T newEntity = (T) entity.getClass().newInstance();
            String[] primaryKeys = entity.getClass().getAnnotation(AsTargetTableMeta.class).primaryKeys();
            for (String fieldName : primaryKeys) {
                Field field = fieldMapCache.get(entity.getClass()).get(fieldName);
                field.setAccessible(true);
                Object value = field.get(entity);
                if (value == null) {
                    throw new DBException("primary key can not be null, field name: " + fieldName);
                }
                field.set(newEntity, field.get(entity));
            }
            return newEntity;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public static <T> QueryWrapper<T> buildQueryWrapper(T entity) throws DBException {
        // 通过反射初始化一个新的对象
        try {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            String[] primaryKeys = entity.getClass().getAnnotation(AsTargetTableMeta.class).primaryKeys();
            for (String fieldName : primaryKeys) {
                Field field = fieldMapCache.get(entity.getClass()).get(fieldName);
                field.setAccessible(true);

                Object value = field.get(entity);
                if (value == null) {
                    throw new DBException("primary key can not be null, field name: " + fieldName);
                }
                wrapper.eq(fieldName, value);
            }
            return wrapper;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public static Map<String, Object> buildDeleteMap(Object entity) throws DBException {
        // 通过反射初始化一个新的对象
        try {
            Map<String, Object> deleteMap = Maps.newHashMap();
            Map<String, Field> fieldMap = fieldMapCache.get(entity.getClass());
            for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
                Field field = entry.getValue();
                field.setAccessible(true);
                Object value = field.get(entity);
                if (value != null) {
                    deleteMap.put(entry.getKey(), value);
                }
            }
            return deleteMap;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public static <T> QueryWrapper<T> buildQueryListWrapper(List<T> entities) throws DBException {
        // 通过反射初始化一个新的对象
        try {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            String[] primaryKeys = entities.get(0).getClass().getAnnotation(AsTargetTableMeta.class).primaryKeys();
            if (primaryKeys.length != 1) {
                throw new DBException("query batch only support one primary key : " + StringUtils.join(primaryKeys, ","));
            }
            String primaryKeyName = primaryKeys[0];
            List<Object> primaryKeyValues = new ArrayList<>();
            for (T entity : entities) {
                Field field = fieldMapCache.get(entity.getClass()).get(primaryKeyName);
                field.setAccessible(true);
                Object value = field.get(entity);
                if (value == null) {
                    throw new DBException("primary key can not be null, field name: " + primaryKeyName);
                }
                primaryKeyValues.add(value);
            }
            wrapper.in(primaryKeyName, primaryKeyValues);
            return wrapper;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public static <T> UpdateWrapper<T> buildUpdateWrapper(T entity) throws DBException {
        try {
            UpdateWrapper<T> wrapper = new UpdateWrapper<>();
            Set<String> primaryKeys = Arrays.stream(entity.getClass().getAnnotation(AsTargetTableMeta.class).primaryKeys()).collect(Collectors.toSet());
            Map<String, Field> fieldMap = fieldMapCache.get(entity.getClass());
            for (String fieldName : fieldMap.keySet()) {
                Field field = fieldMap.get(fieldName);
                if (StringUtils.equalsAny(fieldName, "_id", "created_ts", "updated_ts")) {
                    continue;
                }
                field.setAccessible(true);
                if (primaryKeys.contains(fieldName)) {
                    wrapper.eq(fieldName, field.get(entity));
                    continue;
                }
                if (isSimpleType(field)) {
                    wrapper.set(fieldName, field.get(entity));
                } else {
                    wrapper.set(fieldName, field.get(entity), NO_NULL_MAPPING_JSON);
                }
            }
            return wrapper;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    public static <T> UpdateWrapper<T> buildUpdateTsWrapper(T entity) throws DBException {
        try {
            UpdateWrapper<T> wrapper = new UpdateWrapper<>();
            Set<String> primaryKeys = Arrays.stream(entity.getClass().getAnnotation(AsTargetTableMeta.class).primaryKeys()).collect(Collectors.toSet());
            Map<String, Field> fieldMap = fieldMapCache.get(entity.getClass());
            primaryKeys.add("update_ts");
            for (String fieldName : primaryKeys) {
                Field field = fieldMap.get(fieldName);
                field.setAccessible(true);
                if (StringUtils.equals(fieldName, "update_ts")) {
                    if (field.get(entity) == null) {
                        wrapper.set(fieldName, System.currentTimeMillis());
                    }
                } else {
                    wrapper.eq(fieldName, field.get(entity));
                }
            }
            return wrapper;
        } catch (Exception e) {
            throw new DBException(e);
        }
    }

    protected static boolean isSimpleType(Field field) {
        return field.getType().isPrimitive() || field.getType().equals(String.class) || field.getType().equals(Integer.class)
                || field.getType().equals(Long.class) || field.getType().equals(Double.class) || field.getType().equals(Float.class)
                || field.getType().equals(Boolean.class) || field.getType().equals(Short.class) || field.getType().equals(Byte.class)
                || field.getType().equals(Character.class);
    }

}
